package edu.uwm.RobberKnight.View;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.util.LinkedList;

import javax.swing.JPanel;

import edu.uwm.RobberKnight.Controller.RobberKnightsController;
import edu.uwm.RobberKnight.Model.GameState;
import edu.uwm.RobberKnight.Model.Player;
import edu.uwm.RobberKnight.Model.Tile;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;

/*
 * The GUI component that represents the board for a game of Robber Knights.
 */
public class BoardView extends JPanel {

    private static final long serialVersionUID = -2023284659473762091L;
    private RobberKnightsController _controller;
    private int _offsetX;
    private int _offsetY;
    private Point _lastClicked;
    private int _validLocAlpha;
    private boolean _validLocAlphaUp;
    private LinkedList<GuiTile> _tiles;
    private Image _add;
    public static int SIZE = GuiTile.TILE_SIZE;
    public static int WEIGHT = 2;
    public static int QUARTER = SIZE / 4;
    private Tile _displayInfo;
    private String _inst;
    private Font _defaultText;
    private Font _winnerText;
    private String _winText = "has won the game!";
    private String _tieText = "have tied!";
    private FireworkShow _fireworks;

    public BoardView(RobberKnightsController controller) {
        addMouseListener(new MouseAdapter() {

        	/*
        	 * Support for dragging the map area.
        	 */
            @Override
            public void mousePressed(MouseEvent e) {
                _lastClicked = e.getPoint();
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                _lastClicked = null;
            }
            
            /*
             * Use the clicked event for placing tiles and knights.
             */
            @Override
            public void mouseClicked(MouseEvent e) {

                if (_controller.getSelectedTile() != null) {
                    Point coords = screenToBoard(e.getX(), e.getY());
                    _controller.placeTile(coords.x, coords.y);
                }
                if (_controller.isKnightsPlayable()) {
                    for (GuiTile gt : _tiles) {
                        Point coords = screenToBoard(e.getX(), e.getY());
                        if (gt.getTile().getPosition().equals(coords)) {
                            _controller.placeKnight(gt.getTile());
                        }
                    }
                }
            }
        });
        addMouseMotionListener(new MouseMotionAdapter() {

        	/*
        	 * Drag event will change the x and y offsets which allows the board to be dragged.
        	 */
            @Override
            public void mouseDragged(MouseEvent event) {
                if (_lastClicked != null) {
                    Point point = event.getPoint();
                    int newOffsetX = _offsetX + point.x - _lastClicked.x;
                    int newOffsetY = _offsetY + point.y - _lastClicked.y;
                    if ((newOffsetX > _offsetX && newOffsetX <= 100)
                            || (newOffsetX < _offsetX && newOffsetX >= (GuiTile.TILE_SIZE * _controller.getBoardWidth() * -1) + getWidth() - 100)) {
                        _offsetX = newOffsetX;
                    }
                    if ((newOffsetY > _offsetY && newOffsetY <= 100)
                            || (newOffsetY < _offsetY && newOffsetY >= (GuiTile.TILE_SIZE * _controller.getBoardHeight() * -1) + getHeight() - 100)) {
                        _offsetY = newOffsetY;
                    }
                    _lastClicked = point;
                }
            }
            
            /*
             * Display the detailed tile info in the top right corner when a tile has the cursor over it.
             */
        	@Override
        	public void mouseMoved(MouseEvent evt) {
        		//During initial tile placement the tiles are face down and should be hidden.
        		if(_controller.getState() == GameState.initialTilePlacement)
        			return;
        		Point p = screenToBoard(evt.getX(), evt.getY());
        		boolean tileFound = false;
        		for(GuiTile gui : _tiles) {
        			Tile tile = gui.getTile();
        			if(tile.getPosition().x == p.x && tile.getPosition().y == p.y){
        				_displayInfo = tile;
        				tileFound = true;
        				break;
        			}
        		}
        		if(!tileFound)
        			_displayInfo = null;
        	}
        });
        _controller = controller;
        _tiles = new LinkedList<GuiTile>();
        _add = Resources.getAddIcon();
        _winnerText = new Font("Arial", Font.BOLD, 40);
        _defaultText = new Font("Tahoma", Font.BOLD, 15);
    }

    /*
     * Update the time dependent properties.
     */
    public void updateState(long time) {
        //Make sure our tile collection matches the board.
        updateTiles();

        if (_validLocAlphaUp) {
            _validLocAlpha += (time / 1000f) * 256;
            if (_validLocAlpha > 255) {
                _validLocAlphaUp = false;
                _validLocAlpha = 255;
            }
        } else {
            _validLocAlpha -= (time / 1000f) * 256;
            if (_validLocAlpha < 0) {
                _validLocAlphaUp = true;
                _validLocAlpha = 0;
            }
        }
        if(_fireworks != null)
        	_fireworks.updateState(time, getWidth(), getHeight());
    }

    /*
     * Ensure the GUI's tile collection is in sync with the domain.
     * This only adds new tiles, tiles should never be removed from the board.
     */
    private void updateTiles() {
        for (Tile tile : _controller.getTiles()) {
            boolean contains = false;
            for (GuiTile gt : _tiles) {
                if (gt.getTile() == tile) {
                    contains = true;
                    break;
                }
            }
            if (!contains) {
                _tiles.add(new GuiTile(tile));
            }
        }
    }

    /*
     * Draw the BoardView.
     */
    @Override
    public void paint(Graphics g) {
        g.translate(_offsetX, _offsetY);

        //Draw dark grey background (behind the board).
        g.setColor(Color.GRAY);
        g.fillRect(-_offsetX, -_offsetY, getWidth(), getHeight());
        
        //Draw the black background.
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, _controller.getBoardWidth() * GuiTile.TILE_SIZE, _controller.getBoardHeight() * GuiTile.TILE_SIZE);

        //Draw all the tiles.
        for (GuiTile tile : _tiles) {
            Point coords = boardToScreen(tile.getTile().xPosition(), tile.getTile().yPosition());
            tile.setX(coords.x);
            tile.setY(coords.y);
            if (_controller.getState() != GameState.initialTilePlacement) {
                tile.drawTileFace(g, true);
            } else {
                tile.drawTileBack(g);
            }
        }
        // Draw cross hairs denoting valid tile placements.
        Color white = new Color(255, 255, 255, _validLocAlpha);
        if (!_controller.isKnightsPlayable() || _controller.getSelectedTile() != null) {
            for (Point p : _controller.getValidTileLocs()) {
                drawCrosshairs(g, p, white);
            }
        }
        if (_controller.isKnightsPlayable()) {
            LinkedList<Tile> validKnightLocs = _controller.getValidKnightMoves();
            Color red = new Color(255, 0, 0, _validLocAlpha);
            for (Tile t : validKnightLocs) {
                Point p = t.getPosition();
                int x = (p.x * SIZE) + SIZE / 2;
                int y = p.y * SIZE + WEIGHT;
                g.drawImage(_add, x, y, 16, 16, null);
                //TODO: Update this when the Issue #1 is fixed.
                drawOutlinedText(g, "+ " + _controller.getMinKnight(t), Color.WHITE, Color.BLACK, x + 20, y + 10);
                drawCrosshairs(g, p, red);
            }
        }
        drawTileInfo(g);
        drawNumbersGrid(g);
        drawInstInfo(g);
        drawWinners(g);
    }
    
    /*
     * Draw the coordinate numbers at the top and left sides of the screen.
     */
    public void drawNumbersGrid(Graphics f) 
    {
    	f.setFont(_defaultText);
		// Draw vertical numbers
		int vertical_Y = 40;
		int vertical_Y_Offset = GuiTile.TILE_SIZE;
		String vertical_number = "0";
		f.setColor(Color.WHITE);
		
			for(int i = 0; i < _controller.getBoardHeight(); i++)
			{
				drawOutlinedText(f, vertical_number,Color.WHITE, Color.BLACK, 2 - _offsetX, vertical_Y);
				vertical_Y = vertical_Y + vertical_Y_Offset;
				int y_num = Integer.parseInt(vertical_number);
				y_num++;
				vertical_number= Integer.toString(y_num);
			}
		
		// Draw horizontal numbers 
		int horizontal_x = 35;
		int horizontal_x_offset = GuiTile.TILE_SIZE;
		String horizontal_number = "0";
       
			for(int i = 0; i < _controller.getBoardWidth(); i++)
			{
				drawOutlinedText(f, horizontal_number,Color.WHITE, Color.BLACK, horizontal_x, 14 - _offsetY);
				horizontal_x = horizontal_x + horizontal_x_offset;
				int x_num = Integer.parseInt(horizontal_number);
				x_num++;
				horizontal_number= Integer.toString(x_num);			
			}	
    }
    
    /*
     * When the mouse is hovering above a tile display the information pertaining to that tile.
     */
    public void drawTileInfo(Graphics g) {
    	if(_displayInfo != null) {
    		g.setFont(_defaultText);
    		drawOutlinedText(g, _displayInfo.getTileType().toString(), Color.WHITE, Color.BLACK, getWidth() - _offsetX - 150, 30 - _offsetY);
    		drawOutlinedText(g, "Knights needed: " + (_displayInfo.isPassable() ? _displayInfo.getMinDefaultKnight() : "-"), 
    				Color.WHITE, Color.BLACK, getWidth() - _offsetX - 150, 45 - _offsetY);
    		drawOutlinedText(g, "Points: " + _displayInfo.getScore(), Color.WHITE, Color.BLACK, getWidth() - _offsetX - 150, 60 - _offsetY);
    	}
    }
    
    /*
     * Notify the player of the valid choices they can make for this turn.
     */
    public void drawInstInfo(Graphics g){
    	setInstText();
    	if(_inst != null){
    		drawOutlinedText(g, _inst, Color.WHITE, Color.BLACK, getWidth()/2 - _offsetX, getHeight() - _offsetY - 10); //adjust position??
    	}
    }

    /*
     * Draw blinking crosshairs at the specified point.
     */
    public void drawCrosshairs(Graphics g, Point p, Color color) {
        g.setColor(color);
        // upper left
        g.fillRect(p.x * SIZE + WEIGHT, p.y * SIZE + WEIGHT, WEIGHT,
                QUARTER);
        g.fillRect(p.x * SIZE + WEIGHT, p.y * SIZE + WEIGHT, QUARTER,
                WEIGHT);

        // upper right
        g.fillRect((p.x * SIZE) + SIZE - QUARTER - WEIGHT, p.y * SIZE
                + WEIGHT, QUARTER, WEIGHT);
        g.fillRect((p.x * SIZE) + SIZE - WEIGHT, p.y * SIZE + WEIGHT,
                WEIGHT, QUARTER);

        // bottom left
        g.fillRect(p.x * SIZE + WEIGHT, (p.y * SIZE) + SIZE - QUARTER
                - WEIGHT, WEIGHT, QUARTER);
        g.fillRect(p.x * SIZE + WEIGHT, (p.y * SIZE) + SIZE - WEIGHT,
                QUARTER, WEIGHT);

        // bottom right
        g.fillRect((p.x * SIZE) + SIZE - QUARTER, (p.y * SIZE) + SIZE
                - WEIGHT, QUARTER, WEIGHT);
        g.fillRect((p.x * SIZE) + SIZE - WEIGHT, (p.y * SIZE) + SIZE
                - QUARTER - WEIGHT, WEIGHT, QUARTER);
    }
    
    /*
     * When the game has reached its conclusion draw the necesary information to declare a winner.
     */
    private void drawWinners(Graphics g) {
    	LinkedList<Player> winners = _controller.getWinners();
    	if(winners != null) {
    		if(winners.size() == 1) {
    			g.setFont(_winnerText);
    			int nameWidth = g.getFontMetrics().stringWidth(winners.getFirst().getName());
    			int height = g.getFontMetrics().getHeight();
    			drawOutlinedText(g, winners.getFirst().getName(), Color.WHITE, Color.BLACK, 
    					(getWidth() / 2) - (nameWidth / 2) - _offsetX, 
    					(getHeight() / 2) - height - 5 - _offsetY);
    			int winTextWidth = g.getFontMetrics().stringWidth(_winText);
    			drawOutlinedText(g, _winText, Color.WHITE, Color.BLACK, 
    					(getWidth() / 2) - (winTextWidth / 2) - _offsetX, 
    					(getHeight() / 2) - _offsetY);
    		}
    		else if(winners.size() > 1) {
    			g.setFont(_winnerText);
    			for(int i = 0; i < winners.size(); i++) {
    				Player player = winners.get(i);
    				int nameWidth = g.getFontMetrics().stringWidth(player.getName());
    				int height = g.getFontMetrics().getHeight();
    				drawOutlinedText(g, player.getName(), Color.WHITE, Color.BLACK,
    						(getWidth() / 2) - (nameWidth / 2) - _offsetX, 
    						(getHeight() / 2) - ((height + 5) * (i+ 1)) - _offsetY);
    				int tieTextWidth = g.getFontMetrics().stringWidth(_tieText);
    				drawOutlinedText(g, _tieText, Color.WHITE, Color.BLACK, 
    						(getWidth() / 2) - (tieTextWidth / 2) - _offsetX, 
    						(getHeight() / 2) - _offsetY);
    			}
    		}
        	if(_fireworks == null) {
        		_fireworks = new FireworkShow();
        		_fireworks.start();
        	}
            _fireworks.draw(g, _offsetX, _offsetY);
    	}
    }

    /*
     * Draw text with an outline.
     */
    private static void drawOutlinedText(Graphics g, String message, Color inside, Color outside, int x, int y) {
        g.setColor(outside);
        g.drawString(message, x - 1, y);
        g.drawString(message, x + 1, y);
        g.drawString(message, x, y - 1);
        g.drawString(message, x, y + 1);
        g.setColor(inside);
        g.drawString(message, x, y);
    }

    /*
     * Convert screen coordinates to board coordinates.
     */
    public Point screenToBoard(int x, int y) {
        return new Point((x - _offsetX) / GuiTile.TILE_SIZE, (y - _offsetY) / GuiTile.TILE_SIZE);
    }

    /*
     * Convert board coordinates to screen coordinates.
     */
    public Point boardToScreen(int x, int y) {
        return new Point(x * GuiTile.TILE_SIZE, y * GuiTile.TILE_SIZE);
    }

    /*
     * Set the x and y offsets so that the board is centered on the screen.
     */
    public void centerScreen() {
        _offsetX = -(_controller.getBoardWidth() * GuiTile.TILE_SIZE) / 2 + (getWidth() / 2);
        _offsetY = -(_controller.getBoardHeight() * GuiTile.TILE_SIZE) / 2 + (getHeight() / 2);
    }
    
    /*
     * Call this method to display the information for the specified tile.
     */
    public void setDisplayTile(Tile tile) {
    	_displayInfo = tile;
    }
    
    /*
     * Update the instruction text.
     */
    public String setInstText(){
    	if (_controller.getBoard() != null) {
        	if(_controller.getState().equals(GameState.end)){
        		_inst = "Game Complete";
        	}
        	else{
        		if (_controller.getBoard().isKnightsPlayable()) {
        			if (_controller.getBoard().getPlayableTiles() > 0) {
        				_inst = "Place knights, another tile, or end turn";
        			} else {
        				_inst = "Place knights or end turn";
        			}
        		} else if (_controller.getBoard().getPlayableTiles() > 0) {
        			if (_controller.getBoard().getPlayableTiles() != 3 && _controller.getState().equals(GameState.regularPlay)) {
        				_inst = "Place a tile or end turn";
        			} else {
        				_inst = "Place a tile";
        			}
        		} else {
        			_inst = "Turn complete";
        		}
        	}
        }
    	return _inst;
    }

    @Override
    public int getWidth() {
        return super.getWidth();
    }

    @Override
    public int getHeight() {
        return super.getHeight();
    }
}
